package com.xkcoding.orm.mybatis.plus.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.util.StringUtils;

import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;


/**
 * @desc: demo
 * @name: FTPUtils.java
 * @author: andrew
 * @createTime: 2021年10月12日 下午10:43:27
 * @history:
 * @version: v1.0
 */

@Slf4j
public class FTPUtils {
    public FTPClient ftpClient = new FTPClient();

    public static String LOCAL_CHARSET = "GBK";

    public static String FTP_ROOT = "";

    public FTPUtils() {
        // 设置将过程中使用到的命令输出到控制台
        this.ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
    }
    /**
     *
     * @todo连接到FTP服务器
     * @param hostname 主机名
     * @param port     端口
     * @param username 用户名
     * @param password 密码
     * @return 是否连接成功
     * @throws IOException
     *
     */
    public boolean connect(String hostname, int port, String username, String password) throws IOException {
        ftpClient.connect(hostname, port);
        if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
            if (ftpClient.login(username, password)) {
                // 设置PassiveMode传输
                ftpClient.enterLocalPassiveMode();
                // 设置以二进制流的方式传输
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
//
                if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
                        "OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持，如果服务器支持就用UTF-8编码，否则就使用本地编码（GBK）.
                    LOCAL_CHARSET = "UTF-8";
                }
                System.out.println("FTP编码为"+LOCAL_CHARSET);
                ftpClient.setControlEncoding(LOCAL_CHARSET);
                String root = ftpClient.printWorkingDirectory();
                if (!root.equals("/")){
                    FTP_ROOT = root;
                    System.out.println("服务器根目录为"+FTP_ROOT);
                }else {
                    System.out.println("服务器根目录为"+FTP_ROOT);
                }
                return true;
            }
        }
        disconnect();
        return false;
    }

    /**
     *
     * @todo连接到FTP服务器
     * @param hostname 主机名
     * @param port     端口
     * @return 是否连接成功
     * @throws IOException
     *
     */
    public boolean connect(String hostname, int port) throws IOException {
        ftpClient.connect(hostname, port);
//        ftpClient.setControlEncoding("GBK");
        if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
            ftpClient.login("anonymous", "");
            ftpClient.enterLocalPassiveMode();
            // 设置以二进制流的方式传输
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
                    "OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持，如果服务器支持就用UTF-8编码，否则就使用本地编码（GBK）.
                LOCAL_CHARSET = "UTF-8";
            }
            System.out.println("FTP编码为"+LOCAL_CHARSET);
            ftpClient.setControlEncoding(LOCAL_CHARSET);
                return true;
            }

        disconnect();
        return false;
    }


    /**
     *
     * 从FTP服务器上下载文件,支持断点续传，上传百分比汇报
     * @param remote 远程文件路径
     * @param local  本地文件路径
     * @return 上传的状态
     * @throws IOException
     *
     */
    public DownloadStatus download(String remote, String local) throws IOException {
        // 设置被动模式
        ftpClient.enterLocalPassiveMode();
        // 设置以二进制方式传输
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        DownloadStatus result;
        // 检查远程文件是否存在
        FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes(LOCAL_CHARSET), "iso-8859-1"));
        if (files.length != 1) {
            System.out.println("远程文件不存在");
            return DownloadStatus.Remote_File_Noexist;
        }
        long lRemoteSize = files[0].getSize();
        File f = new File(local);
        // 本地存在文件，进行断点下载
        if (f.exists()) {
            long localSize = f.length();
            // 判断本地文件大小是否大于远程文件大小
            if (localSize >= lRemoteSize) {
                System.out.println("本地文件大于远程文件，下载中止");
                return DownloadStatus.Local_Bigger_Remote;
            }
            // 进行断点续传，并记录状态
            FileOutputStream out = new FileOutputStream(f, true);
            ftpClient.setRestartOffset(localSize);
            InputStream in = ftpClient.retrieveFileStream(new String(remote.getBytes(LOCAL_CHARSET), "iso-8859-1"));
            byte[] bytes = new byte[1024];
            long step = lRemoteSize / 100;
            long process = localSize / step;
            int c;
            while ((c = in.read(bytes)) != -1) {
                out.write(bytes, 0, c);
                localSize += c;
                long nowProcess = localSize / step;
                if (nowProcess > process) {
                    process = nowProcess;
                    if (process % 10 == 0)
                        System.out.println("下载进度：" + process);
                    // TODO 更新文件下载进度,值存放在process变量中
                }
            }
            in.close();
            out.close();
            boolean isDo = ftpClient.completePendingCommand();
            if (isDo) {
                result = DownloadStatus.Download_From_Break_Success;
            } else {
                result = DownloadStatus.Download_From_Break_Failed;
            }
        } else {
            OutputStream out = new FileOutputStream(f);
            InputStream in = ftpClient.retrieveFileStream(new String(remote.getBytes(LOCAL_CHARSET), "iso-8859-1"));
            byte[] bytes = new byte[1024];
            long step = lRemoteSize / 100;
            long process = 0;
            long localSize = 0L;
            int c;
            while ((c = in.read(bytes)) != -1) {
                out.write(bytes, 0, c);
                localSize += c;
                long nowProcess = localSize / step;
                if (nowProcess > process) {
                    process = nowProcess;
                    if (process % 10 == 0)
                        System.out.println("下载进度：" + process);
                    // TODO 更新文件下载进度,值存放在process变量中
                }
            }
            in.close();
            out.close();
            boolean upNewStatus = ftpClient.completePendingCommand();
            if (upNewStatus) {
                result = DownloadStatus.Download_New_Success;
            } else {
                result = DownloadStatus.Download_New_Failed;
            }
        }
        return result;
    }

    /**
     * 上传文件到FTP服务器，支持断点续传
     * @param localFiles  本地文件名称，绝对路径 file("/home/directory1/subdirectory/file.ext")
     * @param remote 远程文件路径，使用/home/directory1/subdirectory/    不加文件
     * 按照Linux上的路径指定方式，支持多级目录嵌套，支持递归创建不存在的目录结构
     * @return 上传结果
     * @throws IOException
     */
    public void upload( String remote,File... localFiles) throws Exception {
        if (StringUtils.isEmpty(remote) || localFiles == null || localFiles.length==0){
            log.error("本地文件为空");
            return;
        }
        UploadStatus result;
        try {
            mkdirs(remote); // 创建文件夹

            for (File file : localFiles) {
                if (file.isDirectory()) { // 上传目录
                    uploadFolder(getURL(remote), file);
                }
                else{
                    // 检查远程是否存在文件
                    FTPFile[] files = ftpClient.listFiles(new String((FTP_ROOT+remote+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                    System.out.println("文件是"+Arrays.asList(files).toString());
                    System.out.println("路径是"+new String((FTP_ROOT+remote+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                    if (files.length == 1) {
                        long remoteSize = files[0].getSize();
                        System.out.println(remoteSize+file.getPath());
                        long localSize = file.length();
                        if (remoteSize == localSize) {
                            System.out.println("不用上传已经有");
                            log.debug("上传成功");
                            continue;
                        } else if (remoteSize > localSize) {
                            log.debug("目标文件过大");
                            continue;
                        }
                        result = uploadFile(file.getName(), file, remoteSize);
                        System.out.println("断点续传");
                        log.debug(result+file.getPath());
                    } else {
                        result = uploadFile(file.getName(), file, 0);
                        System.out.println("新的上传");
                        log.debug(result+file.getPath());
                    }
                    if (result == UploadStatus.Upload_From_Break_Failed || result == UploadStatus.Upload_New_File_Failed) {
                        log.debug(result+file.getPath());
                    }
                }
            }
        }finally {

        }
    }

    /**
     * 上传文件或目录
     *
     * @param dir
     *            目标文件
     *            是否删除源文件，默认为false
     *            文件或目录路径数组
     * @throws Exception
     */
    public void upload(String dir, String... paths)
            throws Exception {
        if (dir==null || paths == null || paths.length ==0) {
            return;
        }
        File[] files = new File[paths.length];
        for (int i = 0; i < paths.length; i++) {
            files[i] = new File(paths[i]);
        }
        upload(dir,files);
    }

    public void uploadFolder(URL parentUrl,File file) throws Exception {
        ftpClient.changeWorkingDirectory(FTP_ROOT+parentUrl.getPath());
        System.out.println("标记1"+FTP_ROOT+parentUrl.getPath());
        String dir = file.getName(); // 当前目录名称
        URL url = getURL(parentUrl, dir);
        if (!exists( url.getPath())) { // 判断当前目录是否存在
            ftpClient.makeDirectory(dir); // 创建目录
            System.out.println("标记3");
        }
        ftpClient.changeWorkingDirectory(dir);
        System.out.println("标记2"+dir);
        File[] files = file.listFiles(); // 获取当前文件夹所有文件及目录
        for (int i = 0; i < files.length; i++) {
            file = files[i];
            if (file.isDirectory()) { // 如果是目录，则递归上传
                uploadFolder(url, file);
            } else { // 如果是文件，直接上传
//                client.changeDirectory(url.getPath());
                // 检查远程是否存在文件
                UploadStatus result;
                FTPFile[] ftpFiles = ftpClient.listFiles(new String((FTP_ROOT+url.getPath()+"/"+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                System.out.println("文件是"+Arrays.asList(ftpFiles).toString());
                System.out.println("路径是"+new String((FTP_ROOT+url.getPath()+"/"+file.getName()).getBytes(LOCAL_CHARSET), StandardCharsets.ISO_8859_1));
                if (ftpFiles.length == 1) {
                    System.out.println();
                    long remoteSize = ftpFiles[0].getSize();
                    long localSize = file.length();
                    if (remoteSize == localSize) {
                        System.out.println("不用上传已经有");
                        log.debug("上传成功");
                        continue;
                    } else if (remoteSize > localSize) {
                        log.debug("目标文件过大");
                        continue;
                    }
                    result = uploadFile(file.getName(), file, remoteSize);
                    log.debug(result+file.getPath());
                } else {
                    result = uploadFile(file.getName(), file, 0);
                    log.debug(result+file.getPath());
                }
                if (result == UploadStatus.Upload_From_Break_Failed || result == UploadStatus.Upload_New_File_Failed) {
                    log.debug(result+file.getPath());

                }




            }
        }
    }

    /**
     * 判断文件或目录是否存在
     *
     *            FTP客户端对象
     * @param dir
     *            文件或目录
     * @return
     * @throws Exception
     */
    private boolean exists(String dir) throws Exception {
        return getFileType(dir) != -1;

    }

    /**
     * 判断当前为文件还是目录
     *
     *            FTP客户端对象
     * @param dir
     *            文件或目录
     * @return -1、文件或目录不存在 0、文件 1、目录
     */
    private int getFileType( String dir) {
        FTPFile[] files = null;
        try {
            files = ftpClient.listFiles(FTP_ROOT+dir);  //不存在目录也不报错
        } catch (Exception e) {
            System.out.println("标记a");
            return -1;
        }
        if (files.length > 1) {
            System.out.println("标记b");
            return FTPFile.DIRECTORY_TYPE;
        } else if (files.length == 1) {
            FTPFile f = files[0];
            if (f.getType() == FTPFile.DIRECTORY_TYPE) {
                System.out.println("标记c");
                return FTPFile.DIRECTORY_TYPE;
            }
            String path = dir + "/" + f.getName();
            try {
                int len = ftpClient.listFiles(FTP_ROOT+path).length;
                if (len == 1) {
                    System.out.println("标记d");
                    return FTPFile.DIRECTORY_TYPE;
                } else {
                    System.out.println("标记e");
                    return FTPFile.FILE_TYPE;
                }
            } catch (Exception e) {
                System.out.println("标记f");
                return FTPFile.FILE_TYPE;
            }
        } else {
            try {
                if (!ftpClient.changeWorkingDirectory(FTP_ROOT+dir)){
                    return -1;
                }
                ftpClient.changeToParentDirectory();
                System.out.println("标记g");
                return FTPFile.DIRECTORY_TYPE;
            } catch (Exception e) {
                System.out.println("标记h");
                return -1;
            }
        }
    }

    /**
     * 断开与远程服务器的连接
     * @throws IOException
     *
     */
    public void disconnect() throws IOException {
        if (ftpClient.isConnected()) {
            ftpClient.disconnect();
        }
    }
    /**
     *
     * 递归创建远程服务器目录
     * @param remote    远程服务器文件绝对路径
     * @return 目录创建是否成功
     * @throws IOException
     *
     */
//    public UploadStatus CreateDirecroty(String remote) throws IOException {
//        UploadStatus status = UploadStatus.Create_Directory_Success;
//        String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
//        if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(FTP_ROOT+new String(directory.getBytes(LOCAL_CHARSET), "iso-8859-1"))) {
//            // 如果远程目录不存在，则递归创建远程服务器目录
//            int start = 0;
//            int end = 0;
//            if (directory.startsWith("/")) {
//                start = 1;
//            } else {
//                start = 0;
//            }
//            end = directory.indexOf("/", start);
//            if (!StringUtils.isEmpty(FTP_ROOT)){
//                ftpClient.changeWorkingDirectory(FTP_ROOT);
//            }
//            while (true) {
//                String subDirectory = new String(remote.substring(start, end).getBytes(LOCAL_CHARSET), "iso-8859-1");
//                if (!ftpClient.changeWorkingDirectory(subDirectory)) {
//                    if (ftpClient.makeDirectory(subDirectory)) {
//                        ftpClient.changeWorkingDirectory(subDirectory);
//                    } else {
//                        System.out.println("创建目录失败");
//                        return UploadStatus.Create_Directory_Fail;
//                    }
//                }
//                start = end + 1;
//                end = directory.indexOf("/", start);
//                // 检查所有目录是否创建完毕
//                if (end <= start) {
//                    break;
//                }
//            }
//        }
//        return status;
//    }

    /**
     * 创建目录
     *            FTP客户端对象
     * @param dir
     *            目录
     * @throws Exception
     */
    private void mkdirs(String dir) throws Exception {
        if (dir == null || dir.equals("")) {
            log.error("目标位置为空");
            return;
        }
        dir = FTPPathToolkit.formatPathFTP(dir);
        dir = dir.replace("//", "/");
        String[] dirs = dir.split("/");
        String path = "";
        if (!StringUtils.isEmpty(FTP_ROOT)){
                ftpClient.changeWorkingDirectory(FTP_ROOT);
            }
        for (int i = 0; i < dirs.length; i++) {
            path  +=  dirs[i] + "/";
            if (!isDirExist(path)) {
                if (!ftpClient.makeDirectory(dirs[i])){
                    System.out.println("目录创建失败");
                    throw new Exception();
                }
                System.out.println("创建目录"+path);
                ftpClient.changeWorkingDirectory(FTP_ROOT+path);// 进入创建的目录
            }
        }
    }

    //检查目录是否存在
    private boolean isDirExist(String dir) {
        try {
            if (!ftpClient.changeWorkingDirectory(FTP_ROOT+dir)){
                return false;
            }
            System.out.println("切换目录"+FTP_ROOT+dir);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     *
     * 上传文件到服务器,新上传和断点续传
     * @param remoteFile  远程文件名，在上传之前已经将服务器工作目录做了改变
     * @param localFile   本地文件File句柄，绝对路径
     * @return
     * @throws IOException
     *
     */
    public UploadStatus uploadFile(String remoteFile, File localFile, long remoteSize)
            throws IOException {
        UploadStatus status;
        // 显示进度的上传
        long step = localFile.length() / 100;
        long process = 0;
        long localreadbytes = 0L;
        RandomAccessFile raf = new RandomAccessFile(localFile, "r");
        OutputStream out = ftpClient.appendFileStream(new String(remoteFile.getBytes(LOCAL_CHARSET), "iso-8859-1"));
        // 断点续传
        if (remoteSize > 0) {
            System.out.println("断续续传！"+remoteSize);
            ftpClient.setRestartOffset(remoteSize);
            process = remoteSize / step;
            raf.seek(remoteSize);
            localreadbytes = remoteSize;
        }
        System.out.println("上传！"+remoteSize);
        byte[] bytes = new byte[1024];
        int c;
        while ((c = raf.read(bytes)) != -1) {
            out.write(bytes, 0, c);
            localreadbytes += c;
//            if (localreadbytes / step != process) {
//                process = localreadbytes / step;
//                System.out.println("上传进度:" + process);
//                // TODO 汇报上传状态
//            }
        }
        out.flush();
        raf.close();
        out.close();
        boolean result = ftpClient.completePendingCommand();
        if (remoteSize > 0) {
            status = result ? UploadStatus.Upload_From_Break_Success : UploadStatus.Upload_From_Break_Failed;
        } else {
            status = result ? UploadStatus.Upload_New_File_Success : UploadStatus.Upload_New_File_Failed;
        }
        return status;
    }

    /**
     * 获取FTP目录
     *
     * @param url
     *            原FTP目录
     * @param dir
     *            目录
     * @return
     * @throws Exception
     */
    private URL getURL(URL url, String dir) throws Exception {
        String path = url.getPath();
        if (!path.endsWith("/") && !path.endsWith("//")) {
            path += "/";
        }
        dir = dir.replace("//", "/");
        if (dir.startsWith("/")) {
            dir = dir.substring(1);
        }
        path += dir;
        return new URL(url, path);
    }
    /**
     * 获取FTP目录
     *
     * @param dir
     *            目录
     * @return
     * @throws Exception
     */
    private URL getURL(String dir) throws Exception {
        if (dir.startsWith("/")) {
            dir = dir.substring(1);
        }
        return getURL(new URL("http://8.8.8.8"), dir);
    }

    /**
     * 测试方法
     * @author: tompai
     * @createTime: 2019年12月24日 下午11:08:24
     * @history:
     * @param args void
     */
    public static void main(String[] args) {
        FTPUtils myFtp = new FTPUtils();
        try {
//            Ftp ftp = new Ftp();
            System.out.println("编码为"+ FTPUtils.LOCAL_CHARSET);
//            System.err.println(myFtp.connect("221.224.163.15", 21, "FIH", "TqyjaGLxfGzj%AVE"));
            System.err.println(myFtp.connect("10.132.240.89", 21, "it", "123456"));
//            projectName=new String(projectName.getBytes("UTF-8"),"iso-8859-1");
            System.out.println("编码为"+ FTPUtils.LOCAL_CHARSET);
//            myFtp.upload("D:/3/测试.txt","/smart/测试.txt");
//            System.out.println(myFtp.ftpClient.printWorkingDirectory());
//            System.out.println(myFtp.ftpClient.changeWorkingDirectory(FTPUtils.FTP_ROOT+"/log"));
//            myFtp.ftpClient.changeWorkingDirectory(new String("DebugLog".getBytes(FTPUtils.LOCAL_CHARSET),"iso-8859-1"));
            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(FTP_ROOT+"/log/DebugLog/")));
//            System.out.println(myFtp.ftpClient.changeWorkingDirectory(FTPUtils.FTP_ROOT+"/smart"));
//            System.out.println(myFtp.ftpClient.makeDirectory());
            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(FTP_ROOT+"/smart/202008/202008241228512c103adcaf484989b6126168be788915.pdf")));
            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(FTP_ROOT+"/smart/202008/")));
            System.out.println(Arrays.toString(myFtp.ftpClient.listFiles(FTP_ROOT+"/smaasdasrt/20asdasd2008/")));
//            myFtp.ftpClient.changeWorkingDirectory(new String("权利的游戏".getBytes("GBK"),"iso-8859-1"));
//            System.out.println(myFtp.upload("E:\\test1.docx", "/test1.docx"));
//            System.out.println(myFtp.download("/test1.docx", "E:\\/test2.docx"));
            myFtp.disconnect();
        } catch (IOException e) {
            System.out.println("连接FTP出错：" + e.getMessage());
        }
    }
}
